/* 
 *  Copyright (C) 2008 cooleyes
 *  eyes.cooleyes@gmail.com 
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */
 
#include "audio_player.h"
#include <pspkernel.h>
#include <pspctrl.h>
#include <psppower.h>
#include <pspdebug.h>
#include <psprtc.h>
#include <pspsdk.h>
#include <pspaudio.h>
#include <psputility.h>
#include <stdio.h> 
#include <stdlib.h>
#include <string.h>
#include <sys/unistd.h>

#include "../common/mem64.h"
#include "../parsers/pcm_parser.h"
#include "../parsers/oggvorbis_parser.h"
#include "../parsers/flac_parser.h"
#include "../parsers/mac_parser.h"
#include "../parsers/aa3_parser.h"
#include "../parsers/at3_parser.h"
#include "../parsers/aac_parser.h"
#include "../parsers/m4a_parser.h"
#include "../parsers/mp3_parser.h"
#include "../parsers/wavpack_parser.h"
#include "../parsers/tta_parser.h"
#include "../parsers/wma_parser.h"
#include "../parsers/musepack_parser.h"
#include "cooleyesBridge.h"

char* AudioPlayer::init_hw_codec() {
  
  int result;
  
  result = sceUtilityLoadAvModule(PSP_AV_MODULE_AVCODEC); 

  if ( result < 0 ) return "AudioPlayer : load avcodec.prx fail";

  result = sceUtilityLoadAvModule(PSP_AV_MODULE_ATRAC3PLUS);
  if ( result < 0 ) return "AudioPlayer : load libatrac3plus.prx fail";
  
  char filename[256];
  
  int devkitVersion = sceKernelDevkitVersion();
  
  SceUID modid;
  int status;
  
  getcwd(filename, 256);
  if ( devkitVersion < 0x03050000)
    strcat(filename, "/MODULES/libasfparser330.prx");
  else if ( devkitVersion < 0x03070000)
    strcat(filename, "/MODULES/libasfparser350.prx");
  else
    strcat(filename, "/MODULES/libasfparser370.prx");
  modid = sceKernelLoadModule(filename, 0, NULL);
  if(modid >= 0) {
    modid = sceKernelStartModule(modid, 0, 0, &status, NULL);
  }
  else {
    return "AudioPlayer : load libasfparser.prx fail";
  }
  
  getcwd(filename, 256);
  strcat(filename, "/MODULES/cooleyesBridge.prx");
  modid = sceKernelLoadModule(filename, 0, NULL);
  if(modid >= 0) {
    modid = sceKernelStartModule(modid, 0, 0, &status, NULL);
  }
  else {
    return "AudioPlayer : load cooleyesBridge.prx fail";
  }
  
  cooleyesMeBootStart(devkitVersion, 3);
  
  return 0;
}

AudioPlayer* AudioPlayer::create_audio_player(file_type_enum type) {
  AudioPlayer* player = 0;
  if ( type == FS_PCM_FILE )
    player = new AudioPlayer(new PcmParser() );
  else if ( type == FS_MAC_FILE )
    player = new AudioPlayer(new MonkeysAudioParser());
  else if ( type == FS_FLAC_FILE )
    player = new AudioPlayer(new FlacParser());
  else if ( type == FS_WAVPACK_FILE )
    player = new AudioPlayer(new WavpackParser());
  else if ( type == FS_TTA_FILE )
    player = new AudioPlayer(new TtaParser());
  else if ( type == FS_AAC_FILE )
    player = new AudioPlayer(new AacParser());
  else if ( type == FS_M4A_FILE )
    player = new AudioPlayer(new M4aParser());
  else if ( type == FS_OGG_FILE )
    player = new AudioPlayer(new OggVorbisParser());
  else if ( type == FS_MP3_FILE )
    player = new AudioPlayer(new Mp3Parser());
  else if ( type == FS_MPC_FILE )
    player = new AudioPlayer(new MusepackParser());
  else if ( type == FS_AT3_FILE )
    player = new AudioPlayer(new At3Parser());
  else if ( type == FS_AA3_FILE )
    player = new AudioPlayer(new Aa3Parser());
  else if ( type == FS_WMA_FILE )
    player = new AudioPlayer(new WmaParser());
  
  return player;
}

int AudioPlayer::agent(SceSize input_length, void *input) {
  AudioPlayer* p = (AudioPlayer *) (*((void **) input));
  p->loop();
  return 0;
}

AudioPlayer::AudioPlayer(AudioParser* parser, bool play_seekable) {
  initialize();
  this->parser = parser;
  this->play_seekable = play_seekable;
}

AudioPlayer::~AudioPlayer() {
  close();
}

void AudioPlayer::initialize() {
  parser = 0;
  
  parser_open = false;
  player_open = false;
  srcchannel_open = false;
  
  start_time = 0;
  end_time = 0;
  current_time = 0;
  
  cached_pcm_buffer = 0;
  cached_samples = 0;
  
  pcm_buffer_index = 0;
  pcm_samples = 0;
  
  pcm_buffer[0] = 0;
  pcm_buffer[1] = 0;
  
  play_thread = -1;
  
  play_pause = false;
  play_eof = true;
  play_seekable = true;
  play_seek = false;
  
  current = 0;
  duration = 0;
  seek_to = 0;
}

void AudioPlayer::finalize() {
  
  if ( parser ) {
    if ( parser_open )
      parser->close();
    delete parser;
  }
  
  if ( cached_pcm_buffer ) {
    free_64(cached_pcm_buffer);
  }
  
  if ( pcm_buffer[0] ) {
    free_64(pcm_buffer[0]);
  }
  
  if ( pcm_buffer[1] ) {
    free_64(pcm_buffer[1]);
  }
  
  if ( srcchannel_open ) {
    while(sceAudioOutput2GetRestSample() > 0);
    sceAudioSRCChRelease();
  }
  
  if (!(play_thread < 0)) sceKernelDeleteThread(play_thread);
  
  initialize();
}

char* AudioPlayer::open(const char* filename, int64_t start_time, int64_t end_time) {
  if ( !parser ) {
    finalize();
    return "AudioPlayer : no file parser";
  }
  char* result = 0;
  result = parser->open(filename);
  if ( result ) {
    finalize();
    return result;
  }
  
  parser_open = true;
  
  this->current_time = this->start_time = start_time;
  this->end_time = (end_time < 0 ) ? parser->get_duration() : end_time;
  
  if ( (this->start_time < 0) || (this->start_time >= this->end_time) || (this->end_time > parser->get_duration()) ) {
    finalize();
    return "AudioPlayer : audio time error";
  }
  
  duration = (this->end_time - this->start_time);
  
  pcm_samples = parser->get_samples_per_frame();
  
  pcm_buffer[0] = malloc_64( pcm_samples<<2 );
  if ( pcm_buffer[0] == 0 ) {
    finalize();
    return "AudioPlayer : malloc pcm_buffer fail";
  }
  
  pcm_buffer[1] = malloc_64( pcm_samples<<2 );
  if ( pcm_buffer[1] == 0 ) {
    finalize();
    return "AudioPlayer : malloc pcm_buffer fail";
  }
  
  cached_pcm_buffer = malloc_64( (pcm_samples+parser->get_max_samples_per_frame())<<2 );
  if ( cached_pcm_buffer == 0 ) {
    finalize();
    return "AudioPlayer : malloc cached_pcm_buffer fail";
  }
  
  sceAudioSRCChReserve(pcm_samples, parser->get_samplerate(), 2);
  srcchannel_open = true;
  
  play_thread = sceKernelCreateThread("play_thread", agent, 0x8, 0x10000, PSP_THREAD_ATTR_USER, 0);
  if (play_thread < 0){
    finalize();
    return "AudioPlayer : can not create play_thread";
  }
  
  player_open = true;
  
  return 0;
}

void AudioPlayer::close() {
  if ( player_open )
    finalize();
}

char* AudioPlayer::play() {
  if ( ! player_open ) {
    return "AudioPlayer : player is not open";
  }
  if ( play_eof ) {
    seek(0);
    play_eof = false;
    volatile AudioPlayer* p = this;
    sceKernelStartThread(play_thread, 4, &p);
    return 0;
  }
  return "AudioPlayer : player is playing";
}

void AudioPlayer::stop() {
  if( !play_eof ) {
    play_eof = true;
    sceKernelWaitThreadEnd(play_thread, 0);
    current_time = start_time;
    cached_samples = 0;
    current = 0;
    play_seek = false;
  }
}

void AudioPlayer::pause() {
  play_pause = true;
}

void AudioPlayer::resume() {
  play_pause = false;
}

bool AudioPlayer::eof() {
  return play_eof;
}

bool AudioPlayer::is_seekable() {
  return play_seekable;
}

void AudioPlayer::seek(int64_t seek_time) {
  if ( player_open && play_seekable ) {
    if ( !play_pause && !play_seek) {
      seek_to = start_time + seek_time;
      if (seek_to >= end_time )
        seek_to = end_time;
      play_seek = true;
    }
  }
}

int64_t AudioPlayer::get_duration() {
  return duration;
}

int64_t AudioPlayer::get_current() {
  return current;
}

void AudioPlayer::loop() {
  bool parser_eof = false;
  while( !play_eof ) {
    if ( play_pause )
      continue;
    if ( play_seek ) {
      cached_samples = 0;
      current = parser->seek_time(seek_to) - start_time;
      play_seek = false;
      continue;
    }
    if ( cached_samples >= pcm_samples ) {
      memset(pcm_buffer[pcm_buffer_index], 0, pcm_samples<<2);
      memcpy(pcm_buffer[pcm_buffer_index], cached_pcm_buffer, pcm_samples<<2);
      cached_samples -= pcm_samples;
      if ( cached_samples > 0 )
        memmove(cached_pcm_buffer, ((unsigned char*)cached_pcm_buffer)+(pcm_samples<<2), cached_samples<<2);
        
      sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX, pcm_buffer[pcm_buffer_index]);
      pcm_buffer_index = (pcm_buffer_index + 1) % 2;
      
      current += pcm_samples*1000000LL/parser->get_samplerate();
    }
    else {
      if ( parser_eof ) {
        if ( cached_samples > 0 ) {
          memset(pcm_buffer[pcm_buffer_index], 0, pcm_samples<<2);
          memcpy(pcm_buffer[pcm_buffer_index], cached_pcm_buffer, cached_samples<<2);
          
          sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX, pcm_buffer[pcm_buffer_index]);
          pcm_buffer_index = (pcm_buffer_index + 1) % 2;
      
          current += cached_samples*1000000LL/parser->get_samplerate();
          cached_samples-=cached_samples;
        }
        else {
          play_eof = true;
        }
      }
      else {
        void* pcm;
        uint32_t samples;
        if ( current > (end_time-start_time) || parser->get_frame(&pcm, &samples) || samples == 0) {
          parser_eof = true;
        }
        else {
          memcpy(((unsigned char*)cached_pcm_buffer)+(cached_samples<<2), pcm, samples<<2);
          cached_samples+=samples;
        }
      }  
    }
  }

}